package com.aps.algorithm.kpi.template;

import com.aps.algorithm.entity.BasicEquipSchedulingResult;
import org.apache.commons.lang3.StringUtils;
import org.springframework.util.CollectionUtils;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.Duration;
import java.time.LocalDateTime;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * @Author nbZhuozh
 * @Date 2025-11-09  17:57
 *
 * 基于 排程计划 通用的函数计算工具类
 **/
public class KpiMathUtils {

    /**
     * 计算计划中 各工序的平均时长
     * 公式：计划中各工序的实际处理时长和  / 计划中工序数量
     * 适用场景：
     * 1、瓶颈工序识别、优化
     * 例如 ： 发现 工序 x 平均时长 高于 其它工序 时长 ， 需要优化该工序
     * 2、异常预警
     * 例如 : 工序 x 远超 平均时长 20（参数）分钟，连续 3天（参数） 需要识别并预警
     * @param data
     * @return
     */
    public static BigDecimal avg_process(List<BasicEquipSchedulingResult> data){
        if(CollectionUtils.isEmpty(data)){
            return BigDecimal.ZERO;
        }
        //累加 每个工序的 计划使用时间
        List<Long> durations = data.stream().map(KpiMathUtils::calculateDurationMinutes)
                .filter(duration -> duration > 0).collect(Collectors.toList());
        if (CollectionUtils.isEmpty(durations)) {
            return BigDecimal.ZERO;
        }
        //和 （分）
        long totalMinutes = durations.stream().mapToLong(Long::longValue).sum();
        //整个染色体 各工序的计划使用时间的 累加和   /  工序个数  = 平均工序执行时间 （平均工序时长）
        return BigDecimal.valueOf(totalMinutes).divide(BigDecimal.valueOf(durations.size()), 2, RoundingMode.HALF_UP);
    }

    /**
     * 计算设备利用率 （计划维度）
     * 公式：设备利用率 = 计划中 设备实际运行总时长 / 设备在计划中跨度时长
     *
     * 适用场景：
     * 1、 以计划维度 看各个设备在计划中 的 设备利用率
     * 2、 方案比对，各方案各设备的设备利用率
     * 3、
     * @param data
     * @return
     */
    public static Map<String, BigDecimal> use_equipt_percent(List<BasicEquipSchedulingResult> data){
        // 以设备维度对排程记录分组，准备逐台设备做统计
        Map<String, List<BasicEquipSchedulingResult>> byEquip = data.stream()
                .filter(item -> StringUtils.isNotBlank(item.getEquipId()))
                .collect(Collectors.groupingBy(BasicEquipSchedulingResult::getEquipId));
        Map<String, BigDecimal> utilization = new LinkedHashMap<>();
        for (Map.Entry<String, List<BasicEquipSchedulingResult>> entry : byEquip.entrySet()) {
            String equipId = entry.getKey();
            List<BasicEquipSchedulingResult> records = entry.getValue();
            // 设备忙碌时长：累加每条记录的工序执行时长（分钟）
            long busyMinutes = records.stream().map(KpiMathUtils::calculateDurationMinutes)
                    .filter(duration -> duration > 0).mapToLong(Long::longValue).sum();
            //获取设备的各 使用阶段
            Set<LocalDateTime> timePoints = records.stream()
                    .flatMap(record -> Stream.of(record.getProcessStartTime(), record.getProcessEndTime())) // 拆分成时间点
                    .filter(Objects::nonNull).collect(Collectors.toSet());
            long totalWindow = 0; // 设备在排程中的整体时间跨度（分钟）
            if (timePoints.size() >= 2) {
                LocalDateTime min = timePoints.stream().min(LocalDateTime::compareTo).orElse(null);
                LocalDateTime max = timePoints.stream().max(LocalDateTime::compareTo).orElse(null);
                totalWindow = (min != null && max != null) ? Duration.between(min, max).toMinutes() : 0;   // 时间跨度
            }
            //设备利用率
            BigDecimal ratio = BigDecimal.ZERO;
            if(totalWindow > 0) {
                ratio = BigDecimal.valueOf(busyMinutes)
                        .divide(BigDecimal.valueOf(totalWindow), 4, RoundingMode.HALF_UP);
            }
            utilization.put(equipId, ratio);
        }
        return utilization;
    }


    /**
     * 计算 计划中 单个工序实际的 时长 （不包含 等待时间）
     * 单位 分
     * @param record
     * @return
     */
    private static long calculateDurationMinutes(BasicEquipSchedulingResult record) {
        LocalDateTime start = record.getProcessStartTime();
        LocalDateTime end = record.getProcessEndTime();
        if (start == null || end == null || !end.isAfter(start)) {
            return 0;
        }
        return Duration.between(start, end).toMinutes();
    }



}
